// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build aix | darwin | | dragonfly | | freebsd | | | js&wasm | linux | netbsd | | openbsd | solaris | windows 
// /+build aix darwin dragongfly freebsd js wasm，使其无法用于I/O。

package os

import (
	"runtime"
	"syscall"
	"time"
)


// 在支持SetDeadline的文件上，任何挂起的I/O操作都将被取消，并立即返回错误。
// 如果已调用Close，则它将返回一个错误。
func (f *File) Close() error {
	if f == nil {
		return ErrInvalid
	}
	return f.file.close()
}

// read从文件中最多读取len（b）个字节。
// 返回读取的字节数和错误（如果有）。
func (f *File) read(b []byte) (n int, err error) {
	n, err = f.pfd.Read(b)
	runtime.KeepAlive(f)
	return n, err
}

// pread从文件中从字节偏移量off开始读取len（b）字节。
// 返回读取的字节数和错误（如果有）。
// EOF由错误设置为零的零计数发出信号。
func (f *File) pread(b []byte, off int64) (n int, err error) {
	n, err = f.pfd.Pread(b, off)
	runtime.KeepAlive(f)
	return n, err
}

// 将len（b）字节写入文件。
// 返回写入的字节数和错误（如果有）。
func (f *File) write(b []byte) (n int, err error) {
	n, err = f.pfd.Write(b)
	runtime.KeepAlive(f)
	return n, err
}

// pwrite从字节偏移量off开始将len（b）字节写入文件。
// 返回写入的字节数和错误（如果有）。
func (f *File) pwrite(b []byte, off int64) (n int, err error) {
	n, err = f.pfd.Pwrite(b, off)
	runtime.KeepAlive(f)
	return n, err
}

// syscallMode从Go的便携模式位返回系统调用特定的模式位。
func syscallMode(i FileMode) (o uint32) {
	o |= uint32(i.Perm())
	if i&ModeSetuid != 0 {
		o |= syscall.S_ISUID
	}
	if i&ModeSetgid != 0 {
		o |= syscall.S_ISGID
	}
	if i&ModeSticky != 0 {
		o |= syscall.S_ISVTX
	}
	// Go的ModeTemporary没有映射（仅适用于plan9）。
	return
}

// 查看文件中的文档。go:Chmod。
func chmod(name string, mode FileMode) error {
	longName := fixLongPath(name)
	e := ignoringEINTR(func() error {
		return syscall.Chmod(longName, syscallMode(mode))
	})
	if e != nil {
		return &PathError{Op: "chmod", Path: name, Err: e}
	}
	return nil
}

// 查看file.go:（*file.Chmod）中的文档。
func (f *File) chmod(mode FileMode) error {
	if err := f.checkValid("chmod"); err != nil {
		return err
	}
	if e := f.pfd.Fchmod(syscallMode(mode)); e != nil {
		return f.wrapErr("chmod", e)
	}
	return nil
}

// Chown更改命名文件的数字uid和gid。
// 如果文件是符号链接，则会更改链接目标的uid和gid。
// uid或gid为-1表示不更改该值。
// 如果有错误，则类型为*PathError。
// 
// 在Windows或Plan 9上，Chown始终返回syscall.EWINDOWS或
// EPLAN9错误，用*路径错误包装。
func Chown(name string, uid, gid int) error {
	e := ignoringEINTR(func() error {
		return syscall.Chown(name, uid, gid)
	})
	if e != nil {
		return &PathError{Op: "chown", Path: name, Err: e}
	}
	return nil
}

// Lchown更改命名文件的数字uid和gid。
// 如果文件是符号链接，则会更改链接本身的uid和gid。
// 如果有错误，则类型为*PathError。
// 
// 在Windows上，它总是返回syscall.EWINDOWS错误，包装为
// in*PathError。
func Lchown(name string, uid, gid int) error {
	e := ignoringEINTR(func() error {
		return syscall.Lchown(name, uid, gid)
	})
	if e != nil {
		return &PathError{Op: "lchown", Path: name, Err: e}
	}
	return nil
}

// Chown更改命名文件的数字uid和gid。
// 如果有错误，则类型为*PathError。
// 
// 在Windows上，它总是返回syscall.EWINDOWS错误，包装
// in*PathError。
func (f *File) Chown(uid, gid int) error {
	if err := f.checkValid("chown"); err != nil {
		return err
	}
	if e := f.pfd.Fchown(uid, gid); e != nil {
		return f.wrapErr("chown", e)
	}
	return nil
}

// Truncate更改文件的大小。
// 它不会更改I/O偏移量。
// 如果有错误，则类型为*PathError。
func (f *File) Truncate(size int64) error {
	if err := f.checkValid("truncate"); err != nil {
		return err
	}
	if e := f.pfd.Ftruncate(size); e != nil {
		return f.wrapErr("truncate", e)
	}
	return nil
}

// Sync将文件的当前内容提交到稳定存储。
// 通常，这意味着将文件系统最近写入的数据的内存副本刷新到磁盘。
func (f *File) Sync() error {
	if err := f.checkValid("sync"); err != nil {
		return err
	}
	if e := f.pfd.Fsync(); e != nil {
		return f.wrapErr("sync", e)
	}
	return nil
}

// Chtimes更改命名
// 文件的访问和修改时间，类似于Unix utime（）或utimes（）函数。
// 
// 底层文件系统可能会将值截断或四舍五入到精度较低的时间单位。
// 如果有错误，则类型为*PathError。
func Chtimes(name string, atime time.Time, mtime time.Time) error {
	var utimes [2]syscall.Timespec
	utimes[0] = syscall.NsecToTimespec(atime.UnixNano())
	utimes[1] = syscall.NsecToTimespec(mtime.UnixNano())
	if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil {
		return &PathError{Op: "chtimes", Path: name, Err: e}
	}
	return nil
}

// Chdir将当前工作目录更改为文件
// 该文件必须是一个目录。
// 如果有错误，则类型为*PathError。
func (f *File) Chdir() error {
	if err := f.checkValid("chdir"); err != nil {
		return err
	}
	if e := f.pfd.Fchdir(); e != nil {
		return f.wrapErr("chdir", e)
	}
	return nil
}

// 设置读写截止日期。
func (f *File) setDeadline(t time.Time) error {
	if err := f.checkValid("SetDeadline"); err != nil {
		return err
	}
	return f.pfd.SetDeadline(t)
}

// SetReadDudate设置读取截止日期。
func (f *File) setReadDeadline(t time.Time) error {
	if err := f.checkValid("SetReadDeadline"); err != nil {
		return err
	}
	return f.pfd.SetReadDeadline(t)
}

// setWriteDeadline设置写入截止日期。
func (f *File) setWriteDeadline(t time.Time) error {
	if err := f.checkValid("SetWriteDeadline"); err != nil {
		return err
	}
	return f.pfd.SetWriteDeadline(t)
}

// checkValid检查f是否可用。
// 如果不是，则返回一个适当的错误，可能包含操作名op.
func (f *File) checkValid(op string) error {
	if f == nil {
		return ErrInvalid
	}
	return nil
}

// Ignoringeinter进行函数调用，并在返回
// EINTR错误时重复该调用。尽管我们安装了所有带SA#U重启的
// 信号处理程序，但这似乎是必需的：请参阅#22838、#38033、#38836、#40846。
// 另外#20400和#36644是信号处理程序在未设置SA#U重启的情况下安装的问题。所有这些都不是常见的情况，但它们太多了，我们似乎无法避免一个EINTR循环。
func ignoringEINTR(fn func() error) error {
	for {
		err := fn()
		if err != syscall.EINTR {
			return err
		}
	}
}
